///////////////////////////////////////////////////////////////////////////////////
/// OpenGL Mathematics (glm.g-truc.net)
///
/// Copyright (c) 2005 - 2015 G-Truc Creation (www.g-truc.net)
///
/// This half implementation is based on OpenEXR which is Copyright (c) 2002, 
/// Industrial Light & Magic, a division of Lucas Digital Ltd. LLC
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
/// 
/// The above copyright notice and this permission notice shall be included in
/// all copies or substantial portions of the Software.
/// 
/// Restrictions:
///		By making use of the Software for military purposes, you choose to make
///		a Bunny unhappy.
/// 
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
/// THE SOFTWARE.
///
/// @ref core
/// @file glm/detail/type_half.inl
/// @date 2008-08-17 / 2011-06-15
/// @author Christophe Riccio
///////////////////////////////////////////////////////////////////////////////////

namespace glm {
    namespace detail {
        GLM_FUNC_QUALIFIER float overflow() {
            volatile float f = 1e10;

            for (int i = 0; i < 10; ++i)
                f *= f; // this will overflow before the for loop terminates
            return f;
        }

        union uif32 {
            GLM_FUNC_QUALIFIER uif32() :
                    i(0) {}

            GLM_FUNC_QUALIFIER uif32(float f) :
                    f(f) {}

            GLM_FUNC_QUALIFIER uif32(uint32 i) :
                    i(i) {}

            float f;
            uint32 i;
        };

        GLM_FUNC_QUALIFIER float toFloat32(hdata value) {
            int s = (value >> 15) & 0x00000001;
            int e = (value >> 10) & 0x0000001f;
            int m = value & 0x000003ff;

            if (e == 0) {
                if (m == 0) {
                    //
                    // Plus or minus zero
                    //

                    detail::uif32 result;
                    result.i = (unsigned int) (s << 31);
                    return result.f;
                } else {
                    //
                    // Denormalized number -- renormalize it
                    //

                    while (!(m & 0x00000400)) {
                        m <<= 1;
                        e -= 1;
                    }

                    e += 1;
                    m &= ~0x00000400;
                }
            } else if (e == 31) {
                if (m == 0) {
                    //
                    // Positive or negative infinity
                    //

                    uif32 result;
                    result.i = (unsigned int) ((s << 31) | 0x7f800000);
                    return result.f;
                } else {
                    //
                    // Nan -- preserve sign and significand bits
                    //

                    uif32 result;
                    result.i = (unsigned int) ((s << 31) | 0x7f800000 | (m << 13));
                    return result.f;
                }
            }

            //
            // Normalized number
            //

            e = e + (127 - 15);
            m = m << 13;

            //
            // Assemble s, e and m.
            //

            uif32 Result;
            Result.i = (unsigned int) ((s << 31) | (e << 23) | m);
            return Result.f;
        }

        GLM_FUNC_QUALIFIER hdata

        toFloat16(float const &f) {
            uif32 Entry;
            Entry.f = f;
            int i = (int) Entry.i;

            //
            // Our floating point number, f, is represented by the bit
            // pattern in integer i.  Disassemble that bit pattern into
            // the sign, s, the exponent, e, and the significand, m.
            // Shift s into the position where it will go in in the
            // resulting half number.
            // Adjust e, accounting for the different exponent bias
            // of float and half (127 versus 15).
            //

            int s = (i >> 16) & 0x00008000;
            int e = ((i >> 23) & 0x000000ff) - (127 - 15);
            int m = i & 0x007fffff;

            //
            // Now reassemble s, e and m into a half:
            //

            if (e <= 0) {
                if (e < -10) {
                    //
                    // E is less than -10.  The absolute value of f is
                    // less than half_MIN (f may be a small normalized
                    // float, a denormalized float or a zero).
                    //
                    // We convert f to a half zero.
                    //

                    return hdata(s);
                }

                //
                // E is between -10 and 0.  F is a normalized float,
                // whose magnitude is less than __half_NRM_MIN.
                //
                // We convert f to a denormalized half.
                //

                m = (m | 0x00800000) >> (1 - e);

                //
                // Round to nearest, round "0.5" up.
                //
                // Rounding may cause the significand to overflow and make
                // our number normalized.  Because of the way a half's bits
                // are laid out, we don't have to treat this case separately;
                // the code below will handle it correctly.
                //

                if (m & 0x00001000)
                    m += 0x00002000;

                //
                // Assemble the half from s, e (zero) and m.
                //

                return hdata(s | (m >> 13));
            } else if (e == 0xff - (127 - 15)) {
                if (m == 0) {
                    //
                    // F is an infinity; convert f to a half
                    // infinity with the same sign as f.
                    //

                    return hdata(s | 0x7c00);
                } else {
                    //
                    // F is a NAN; we produce a half NAN that preserves
                    // the sign bit and the 10 leftmost bits of the
                    // significand of f, with one exception: If the 10
                    // leftmost bits are all zero, the NAN would turn
                    // into an infinity, so we have to set at least one
                    // bit in the significand.
                    //

                    m >>= 13;

                    return hdata(s | 0x7c00 | m | (m == 0));
                }
            } else {
                //
                // E is greater than zero.  F is a normalized float.
                // We try to convert f to a normalized half.
                //

                //
                // Round to nearest, round "0.5" up
                //

                if (m & 0x00001000) {
                    m += 0x00002000;

                    if (m & 0x00800000) {
                        m = 0;     // overflow in significand,
                        e += 1;     // adjust exponent
                    }
                }

                //
                // Handle exponent overflow
                //

                if (e > 30) {
                    overflow();        // Cause a hardware floating point overflow;

                    return hdata(s | 0x7c00);
                    // if this returns, the half becomes an
                }   // infinity with the same sign as f.

                //
                // Assemble the half from s, e and m.
                //

                return hdata(s | (e << 10) | (m >> 13));
            }
        }

    }//namespace detail
}//namespace glm
